package eva

import (
	"testing"

	"gitcode.com/deyiyangyang/gascheme/lex"
	"gitcode.com/deyiyangyang/gascheme/par"
	"gitcode.com/deyiyangyang/gascheme/obj"
)

// TestLetStarBasic tests basic let* functionality
func TestLetStarBasic(t *testing.T) {
	// Create a let* expression: (let* ((x 2) (y (* x 3))) (+ x y))
	input := "(let* ((x 2) (y (* x 3))) (+ x y))"

	// Parse the expression
	l := lex.NewLexer(input)
	p := par.NewParser(l)
	expr, err := p.ParseExpression()
	if err != nil {
		t.Fatalf("Parse error: %s", err)
	}

	// Create environment and register built-in procedures
	env := obj.NewGlobalEnvironment()
	RegisterBuiltinProcedures(env)

	// Evaluate the expression
	result := Eval(expr, env)

	// Check if we got an error
	if errObj, ok := result.(*obj.Error); ok {
		t.Fatalf("Eval returned error: %s", errObj.Message)
	}

	// Check if the result is an Integer object with value 8 (2 + 2*3)
	integer, ok := result.(*obj.Integer)
	if !ok {
		t.Fatalf("Eval didn't return Integer. Got %T", result)
	}

	if integer.Value != 8 {
		t.Errorf("Eval returned wrong value. Got %d, want 8", integer.Value)
	}
}

// TestLetStarWithNoBindings tests let* expressions with no bindings
func TestLetStarWithNoBindings(t *testing.T) {
	// Create a let* expression: (let* () (+ 2 3))
	input := "(let* () (+ 2 3))"

	// Parse the expression
	l := lex.NewLexer(input)
	p := par.NewParser(l)
	expr, err := p.ParseExpression()
	if err != nil {
		t.Fatalf("Parse error: %s", err)
	}

	// Create environment and register built-in procedures
	env := obj.NewGlobalEnvironment()
	RegisterBuiltinProcedures(env)

	// Evaluate the expression
	result := Eval(expr, env)

	// Check if we got an error
	if errObj, ok := result.(*obj.Error); ok {
		t.Fatalf("Eval returned error: %s", errObj.Message)
	}

	// Check if the result is an Integer object with value 5
	integer, ok := result.(*obj.Integer)
	if !ok {
		t.Fatalf("Eval didn't return Integer. Got %T", result)
	}

	if integer.Value != 5 {
		t.Errorf("Eval returned wrong value. Got %d, want 5", integer.Value)
	}
}

// TestLetStarWithSingleBinding tests let* expressions with a single binding
func TestLetStarWithSingleBinding(t *testing.T) {
	// Create a let* expression: (let* ((x 42)) x)
	input := "(let* ((x 42)) x)"

	// Parse the expression
	l := lex.NewLexer(input)
	p := par.NewParser(l)
	expr, err := p.ParseExpression()
	if err != nil {
		t.Fatalf("Parse error: %s", err)
	}

	// Create environment and register built-in procedures
	env := obj.NewGlobalEnvironment()
	RegisterBuiltinProcedures(env)

	// Evaluate the expression
	result := Eval(expr, env)

	// Check if we got an error
	if errObj, ok := result.(*obj.Error); ok {
		t.Fatalf("Eval returned error: %s", errObj.Message)
	}

	// Check if the result is an Integer object with value 42
	integer, ok := result.(*obj.Integer)
	if !ok {
		t.Fatalf("Eval didn't return Integer. Got %T", result)
	}

	if integer.Value != 42 {
		t.Errorf("Eval returned wrong value. Got %d, want 42", integer.Value)
	}
}

// TestLetStarSequentialBindings tests that let* bindings are evaluated sequentially
func TestLetStarSequentialBindings(t *testing.T) {
	// Create a let* expression: (let* ((x 1) (y (+ x 1)) (z (+ y 1))) (+ x y z))
	input := "(let* ((x 1) (y (+ x 1)) (z (+ y 1))) (+ x y z))"

	// Parse the expression
	l := lex.NewLexer(input)
	p := par.NewParser(l)
	expr, err := p.ParseExpression()
	if err != nil {
		t.Fatalf("Parse error: %s", err)
	}

	// Create environment and register built-in procedures
	env := obj.NewGlobalEnvironment()
	RegisterBuiltinProcedures(env)

	// Evaluate the expression
	result := Eval(expr, env)

	// Check if we got an error
	if errObj, ok := result.(*obj.Error); ok {
		t.Fatalf("Eval returned error: %s", errObj.Message)
	}

	// Check if the result is an Integer object with value 6 (1 + 2 + 3)
	integer, ok := result.(*obj.Integer)
	if !ok {
		t.Fatalf("Eval didn't return Integer. Got %T", result)
	}

	if integer.Value != 6 {
		t.Errorf("Eval returned wrong value. Got %d, want 6", integer.Value)
	}
}

// TestLetStarErrorCases tests error cases for let*
func TestLetStarErrorCases(t *testing.T) {
	// Test case 1: let* with no arguments
	input1 := "(let*)"
	l1 := lex.NewLexer(input1)
	p1 := par.NewParser(l1)
	expr1, err := p1.ParseExpression()
	if err != nil {
		t.Fatalf("Parse error: %s", err)
	}

	env := obj.NewGlobalEnvironment()
	RegisterBuiltinProcedures(env)
	result1 := Eval(expr1, env)
	
	if _, ok := result1.(*obj.Error); !ok {
		t.Errorf("Expected error for let* with no arguments, got %T", result1)
	}

	// Test case 2: let* with only one argument (bindings)
	input2 := "(let* ((x 1)))"
	l2 := lex.NewLexer(input2)
	p2 := par.NewParser(l2)
	expr2, err := p2.ParseExpression()
	if err != nil {
		t.Fatalf("Parse error: %s", err)
	}

	result2 := Eval(expr2, env)
	
	if _, ok := result2.(*obj.Error); !ok {
		t.Errorf("Expected error for let* with only bindings, got %T", result2)
	}

	// Test case 3: Malformed binding (not a list)
	input3 := "(let* (x 1) (+ x 1))"
	l3 := lex.NewLexer(input3)
	p3 := par.NewParser(l3)
	expr3, err := p3.ParseExpression()
	if err != nil {
		t.Fatalf("Parse error: %s", err)
	}

	result3 := Eval(expr3, env)
	
	if _, ok := result3.(*obj.Error); !ok {
		t.Errorf("Expected error for malformed binding, got %T", result3)
	}

	// Test case 4: Binding with wrong number of elements
	input4 := "(let* ((x 1 2)) (+ x 1))"
	l4 := lex.NewLexer(input4)
	p4 := par.NewParser(l4)
	expr4, err := p4.ParseExpression()
	if err != nil {
		t.Fatalf("Parse error: %s", err)
	}

	result4 := Eval(expr4, env)
	
	if _, ok := result4.(*obj.Error); !ok {
		t.Errorf("Expected error for binding with wrong number of elements, got %T", result4)
	}
}